#pragma once
#include "mypretreat.h"
#include<variant>
#include<iostream>

class layer;
class cnn_layer;
class cnn_derived;

class mymodel {
private:
	typedef Eigen::MatrixXd mat;
	enum { layer_limits = 5 };

	//flag of loss function
	int loss_func;

	double lerning_rate;

	//number of layers
	int layer_cnt;


	/*union layerptr {
		layer* x;
		cnn_layer* y;
	};
	layerptr layers[layer_limits];*/
	//store pointer to different layers
	//use C++17 std::variant rather than union, which is safer 
	std::variant<layer*, cnn_layer*> layers[layer_limits];


	//store input of each layer
	mat input_tensor[layer_limits];

	//store grdient of each layer
	mat gradient[layer_limits];

	double dropout;

public:
	mymodel(int cnt = 0, double lr=0.01, int flag=0, double dp=0);
	mymodel(const mymodel& temp);
	~mymodel();

	//add a layer
	void add(const layer& cp);
	void add(const cnn_layer& cp);
	//void add(const cnn_derived& cp);

	//void combine(int numbers, ...);
	//using layers to create a model
	void combine(const layer& a, const layer& b);
	void combine(const layer& a, const layer& b, const layer& c);
	void combine(const cnn_layer& a, const layer& b);
	void combine(const cnn_layer& a, const layer& b, const layer& c);

	/*the declaration and defination of template should be in one .h file, 
	 or get wrong, because a template function can become a true function
	 only after specified*/
	//use variadic templates
	template <class T, class ...Args>
	void combine_template(const T& value, Args... args)
	{
		//fool wrong originla try, we should use T rather than exact type
		//if (value.flag == 0)
			//layers[++layer_cnt] = new layer(value);
		//else if (value.flag == 1)
			//layers[++layer_cnt] = new cnn_layer(value);

		layers[++layer_cnt] = new T(value);
		combine_template(args...);
	}

	//blank params, blank function
	void combine_template();

	//loss=0,squared_error;loss=1,categorical_cossentropy
	//show=0,print accuracy;show=1,print loss
	void compile(double lr, double dp=0.0, int loss = 0);

	//initialization
	void init();

	//run model with specified parms
	void fit(const mat& input, const mat& output,
		int epochs, int batch_size);

	//show real time test accuracy
	void fit(const mat& input, const mat& output,
		int epochs, int batch_size, const mat& test_img,
		const Eigen::VectorXd& test_labels);

	//predict labels according to input
	Eigen::VectorXd predict(const mat& input);

	//predict labels according to input and thus print accuracy
	void evaluate(const mat& input, const Eigen::VectorXd& labels);

	//print model prams
	void describe() const;

	void test();

	void file_export();

	void file_input(const char* ch1, const char* ch2);
};

//produce random 0/1 matrix for drop_out
Eigen::MatrixXd mask(int r, int c, double drop);

//compute  mean-square error
double loss_mean_square(const Eigen::MatrixXd& one_hot, const Eigen::MatrixXd& pred);

//compute cross entropy error
double loss_crossentropy(const Eigen::MatrixXd& one_hot, const Eigen::MatrixXd& pred);




